import tensorflow as tf
import numpy as np


def sep(label=''):
    print('-' * 32, label, '-' * 32, sep='')


# 使用tensorflow框架，建立神经网络，包含1个隐藏层，使用底层代码实现BP反向传播，实现逻辑异或功能。
# （一）导入tensorflow模块，设置随机种子（8分）
tf.compat.v1.set_random_seed(777)

# （二）准备数据集（8分）
# x1,x2,y的数据是0或者1，如下表，x1和x2进行逻辑异或的结果是y
# x1	x2	Y
# 0	0	0
# 0	1	1
# 1	0	1
# 1	1	0
x = np.array([
    [0, 0],
    [0, 1],
    [1, 0],
    [1, 1]
])
y = np.array([0, 1, 1, 0]).reshape(-1, 1)

# （三）初始化X，Y占位符（8分）
ph_x = tf.compat.v1.placeholder(tf.float32, [None, 2], name='placeholder_x')
ph_y = tf.compat.v1.placeholder(tf.float32, [None, 1], name='placeholder_y')

# （四）初始化W1，b1张量（8分）
w1 = tf.Variable(tf.random.normal([2, 3]), dtype=tf.float32, name='w1')
b1 = tf.Variable(tf.random.normal([1, 3]), dtype=tf.float32, name='b1')

# （五）设置隐藏层layer1模型，使用sigmoid函数（8分）
z1 = tf.add(tf.matmul(ph_x, w1), b1, name='z1')
a1 = tf.sigmoid(z1, name='a1')

# （六）初始化W2，b2张量（8分）
w2 = tf.Variable(tf.random.normal([3, 1]), dtype=tf.float32, name='w2')
b2 = tf.Variable(tf.random.normal([1, 1]), dtype=tf.float32, name='b2')

# （七）设置hypothesis预测模型（8分）
z2 = tf.add(tf.matmul(a1, w2), b2, name='z2')
a2 = tf.sigmoid(z2, name='a2')

# （八）设置代价函数（8分）
cost = tf.math.negative(tf.reduce_mean(
    ph_y * tf.math.log(a2) + (1 - ph_y) * tf.math.log(1 - a2)
))

# （九）不能使用梯度下降优化器，自己编写底层代码实现BP反向传播
# 7个公式对应7行代码，每行代码3分，（共21分）
dz2 = tf.subtract(a2, ph_y, name='dz2')  # m x 1
dz1 = tf.multiply(tf.matmul(dz2, tf.transpose(w2)), a1 * (1 - a1), name='dz1')  # m x 3
dw2 = tf.divide(tf.matmul(tf.transpose(a1), dz2), tf.cast(tf.shape(a1)[0], tf.float32), name='dw2')  # 3 x 1
db2 = tf.reduce_mean(dz2, axis=0)  # 1 x 1
dw1 = tf.divide(tf.matmul(tf.transpose(ph_x), dz1), tf.cast(tf.shape(ph_x)[0], tf.float32), name='dw1')  # 2 x 3
db1 = tf.reduce_mean(dz1, axis=0)  # 1 x 3
alpha = 0.1
train = [
    tf.compat.v1.assign(w2, w2 - alpha * dw2),
    tf.compat.v1.assign(w1, w1 - alpha * dw1),
    tf.compat.v1.assign(b2, b2 - alpha * db2),
    tf.compat.v1.assign(b1, b1 - alpha * db1),
]

# accuracy
acc = tf.reduce_mean(tf.cast(tf.equal(a2 > 0.5, y > 0.5), tf.float32), name='acc')

# （十）创建会话，初始化全局变量（5分）
with tf.compat.v1.Session() as sess:
    sess.run(tf.compat.v1.global_variables_initializer())

    # （十一）迭代训练3000次，每300次输出一次cost（5分）
    iters = 3000
    group = 300
    for i in range(iters):
        _, costv, accv = sess.run([train, cost, acc], feed_dict={ph_x: x, ph_y: y})
        if i % group == 0:
            print(f'#{i + 1}, cost = {costv}, acc = {accv}')
    if i % group != 0:
        print(f'#{i + 1}, cost = {costv}, acc = {accv}')

    # （十二）输出预测值、准确度（5分）
    h = sess.run(a2, feed_dict={ph_x: x})
    print(f'预测值: {h}')
    print(f'准确度: {sess.run(acc, feed_dict={ph_x: x, ph_y: y})}')
